
package com.whosly.mysql.protocol.packet.impl.ok;

import com.whosly.mysql.protocol.MySQLStatusFlag;
import com.whosly.mysql.protocol.config.MySQLProtocolConfig;
import com.whosly.mysql.protocol.connection.ConnectionContextHolder;
import com.whosly.mysql.protocol.jdbc.MySQLMessage;
import com.whosly.mysql.protocol.packet.impl.ok.data.OKData;
import com.whosly.mysql.protocol.packet.impl.ok.data.OKDataBuilder;
import com.whosly.mysql.protocol.packet.jdbc.MySQLPacket;
import com.whosly.mysql.protocol.packet.util.NettyBufferUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;

/**
 * From server to client in response to command, if no error and no result set.
 *
 * <pre>
 * Bytes                       Name
 * -----                       ----
 * 1                           field_count/header, always = [00] or [fe] the OK packet header
 * 1-9 (Length Coded Binary)   affected_rows
 * 1-9 (Length Coded Binary)   insert_id
 *
 * if capabilities & CLIENT_PROTOCOL_41 {
 *      2                           server_status
 *      2                           warning_count
 * }elseif capabilities & CLIENT_TRANSACTIONS {
 *      2                           server_status
 * }
 *
 * if capabilities & CLIENT_SESSION_TRACK {
 *      string<lenenc>	info	human readable status information
 *      if status_flags & SERVER_SESSION_STATE_CHANGED {
 *          string<lenenc>	session_state_changes	session state info
 *      }
 * } else {
 *     string<EOF>	info	human readable status information
 * }
 *
 * @see <link>http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#OK_Packet</link>
 * @see <link>https://dev.mysql.com/doc/internals/en/packet-OK_Packet.html</link>
 * </pre>
 *
 * @author 风杨
 * @see <a href="yueny09@163.com">mailTo: yueny09@163.com</a>
 */
public class OkPacket
        extends MySQLPacket
{
    /**
     * 包头标识, 0x00 代表这是一个OK 包
     */
    @Getter
    private byte resultPackType = PACKET_OK;
    /**
     * 影响行数. 相应操作影响的行数，比如一个Update操作的记录是5条，那么这个值就为5
     */
    public long affectedRows;
    /**
     * 索引值
     */
    public long insertId;
    /**
     * 服务器状. 用于表示服务器状态，比如是否是事务模式或者自动提交模式
     */
    public int serverStatus = MySQLStatusFlag.SERVER_STATUS_AUTOCOMMIT.getValue();
    /**
     * 警告数. 上次命令引起的警告数
     */
    public int warningCount;
    /**
     * human readable status information
     * 此次操作的一些额外 msg 信息
     *
     * 服务器消息：服务器返回给客户端的消息，一般为简单的描述性字符串，可选字段。
     */
    public byte[] info;

    /**
     * 是否为resultSet的ok结束包
     * true时包头为FE，且包总长度>9
     */
    @Setter
    private boolean resultSetEOF = false;

    @Override
    public void setPackId(byte packId) {
        super.setPackId(packId);
    }

    @Override
    public void read(byte[] bodyBytes)
    {
        readBody(this, new MySQLMessage(bodyBytes));
    }

    public void write(ChannelHandlerContext ctx) {
        int size = calcPacketSize();
        // default init 256,so it can avoid buff extract
        ByteBuf buffer = ctx.alloc().buffer(size);
        NettyBufferUtil.writeUB3(buffer, size);
        buffer.writeByte(getPackId());

        writeBody(buffer);

        ctx.writeAndFlush(buffer);
    }

    @Override
    public ByteBuf writeBuf(ByteBuf buffer) {
        int size = calcPacketSize();
        NettyBufferUtil.writeUB3(buffer, size);
        buffer.writeByte(getPackId());

        writeBody(buffer);

        return buffer;
    }

    public ByteBuf writeBuf() {
        int size = calcPacketSize();
        ByteBuf buffer = Unpooled.buffer(size);

        writeBuf(buffer);

        return buffer;
    }

    private void writeBody(ByteBuf buffer) {
        if (resultSetEOF) {
            buffer.writeByte(PACKET_EOF);
        } else {
            buffer.writeByte(resultPackType);
        }

        NettyBufferUtil.writeLength(buffer, affectedRows);
        NettyBufferUtil.writeLength(buffer, insertId);
        NettyBufferUtil.writeUB2(buffer, serverStatus);
        NettyBufferUtil.writeUB2(buffer, warningCount);
        if (info != null) {
            NettyBufferUtil.writeWithLength(buffer, info);
        }
    }

    @Override
    public int calcPacketSize() {
        int i = 1;
        i += NettyBufferUtil.getLength(affectedRows);
        i += NettyBufferUtil.getLength(insertId);
        i += 4;  // 2 + 2   服务器状态, 警告数
        if (info != null) {
            i += NettyBufferUtil.getLength(info);
        }
        return i;
    }

    @Override
    public String getPacketInfo() {
        return "MySQL OK Packet";
    }


    /**
     * 等价于
     * <pre>
     *     OkPacket ok = new OkPacket();
     *     ok.affectedRows = 0;
     *     ok.insertId = 0;
     *     ok.setPackId((byte)1);
     *     ok.serverStatus = 2;
     *     ok.write(getCtx());
     * </pre>
     */
    @Deprecated
    public static final byte[] OK = new byte[] { 7, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0 };

    /**
     * 等价于
     * <pre>
     *     OkPacket ok = new OkPacket();
     *     ok.affectedRows = 0;
     *     ok.insertId = 0;
     *     ok.setPackId((byte)2);
     *     ok.serverStatus = 2;
     *     ok.write(getCtx());
     * </pre>
     */
    public static final byte[] AUTH_OK = new byte[] { 7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0 };

    public static OkPacket of(final MySQLMessage mm){
        OkPacket packet = new OkPacket();
        packet.setArgs(mm.readUB3(), mm.read());

        readBody(packet, mm);

        return packet;
    }

    private static void readBody(OkPacket packet, MySQLMessage mm) {
        packet.resultPackType = mm.read();

        packet.affectedRows = mm.readLength();
        packet.insertId = mm.readLength();
        packet.serverStatus = mm.readUB2();
        packet.warningCount = mm.readUB2();
        if (mm.hasRemaining()) {
            packet.info = mm.readBytesWithLength();
        }
    }

    public static OkPacket build(byte packId, long affectedRows, long lastInsertId, String memo){
        return build(packId, affectedRows, lastInsertId, memo,
                0, MySQLStatusFlag.SERVER_STATUS_AUTOCOMMIT.getValue());
    }

    public static OkPacket build(byte packId, long affectedRows, long lastInsertId, String memo,
                                      int warningCount, int serverStatus){
        if(StringUtils.isNotEmpty(memo)){
            return build(packId, affectedRows, lastInsertId,
                    OKDataBuilder.build(ConnectionContextHolder.get(), affectedRows, memo),
                    warningCount, serverStatus);
        }

        return build(packId, affectedRows, lastInsertId,
                OKDataBuilder.build(ConnectionContextHolder.get(), affectedRows),
                warningCount, serverStatus);
    }

    public static OkPacket build(byte packId, OKData okData) {
        return build(packId, okData.getAffectedRows(), 0, okData);
    }

    public static OkPacket build(byte packId, long affectedRows, long lastInsertId, OKData okData){
        OkPacket ok = new OkPacket();
        ok.setPackId(packId);
        ok.affectedRows = affectedRows;
        ok.insertId = lastInsertId;

        if(MySQLProtocolConfig.isOutputExtraInfo && okData != null){
            ok.info = okData.toString().getBytes(StandardCharsets.UTF_8);
        }

        return ok;
    }

    /**
     *
     * @param warningCount 警告数. 上次命令引起的警告数
     * @param serverStatus 服务器状. 用于表示服务器状态，比如是否是事务模式或者自动提交模式
     * @return
     */
    public static OkPacket build(byte packId, long affectedRows, long lastInsertId, OKData okData,
                                      int warningCount, int serverStatus){
        OkPacket ok = new OkPacket();
        ok.setPackId(packId);
        ok.affectedRows = affectedRows;
        ok.insertId = lastInsertId;
        ok.warningCount = warningCount;
        ok.serverStatus = serverStatus;

        if(MySQLProtocolConfig.isOutputExtraInfo && okData != null){
            ok.info = okData.toString().getBytes(StandardCharsets.UTF_8);
        }

        return ok;
    }

}